昨天遇到的這個問題,
KeyError: 'Close'
---> signals = generate_signals(all_data, short_window=40, long_window=100)
確認一下上面的欄位和處理過的資料
print(cleaned_data.columns)
Index(['Volume', '成交金額', 'Open', 'High', 'Low', 'Close', '漲跌價差', '成交筆數'], dtype='object')
print(all_data)
日期 成交股數 成交金額 開盤價 最高價 最低價 收盤價 漲跌價差 成交筆數
0 2019/01/02 0.113 5.948 52.6 53.2 52.4 52.8 0.2 82.0
1 2019/01/03 0.226 11.979 52.8 53.2 52.8 52.8 0.0 122.0
2 2019/01/04 0.739 39.114 53.0 53.1 52.5 53.0 0.2 259.0
3 2019/01/07 0.295 15.643 53.2 53.2 52.7 53.0 0.0 151.0
4 2019/01/08 3.262 180.218 53.0 57.8 52.7 56.6 3.6 1863.0
... ... ... ... ... ... ... ... ... ...
1368 2024/08/21 3.565 2524.408 692.0 720.0 678.0 715.0 27.0 4694.0
1369 2024/08/22 5.574 4162.601 723.0 776.0 723.0 742.0 27.0 7835.0
1370 2024/08/23 6.501 5037.483 727.0 814.0 726.0 807.0 65.0 7660.0
1371 2024/08/26 7.497 6126.218 809.0 860.0 771.0 798.0 -9.0 9371.0
1372 2024/08/27 4.870 3931.727 806.0 834.0 788.0 814.0 16.0 6800.0[1373 rows x 9 columns]
註:這邊是使用上櫃公司,旺矽(6223), 2019~2024 年全部的股價資料
all_data = all_data.rename(columns={
'開盤價': 'Open',
'最高價': 'High',
'最低價': 'Low',
'收盤價': 'Close',
'成交張數': 'Volume'
})
all_data
日期 成交股數 成交金額 Open High Low Close 漲跌價差 成交筆數
0 2019/01/02 0.113 5.948 52.6 53.2 52.4 52.8 0.2 82.0
1 2019/01/03 0.226 11.979 52.8 53.2 52.8 52.8 0.0 122.0
2 2019/01/04 0.739 39.114 53.0 53.1 52.5 53.0 0.2 259.0
3 2019/01/07 0.295 15.643 53.2 53.2 52.7 53.0 0.0 151.0
4 2019/01/08 3.262 180.218 53.0 57.8 52.7 56.6 3.6 1863.0
... ... ... ... ... ... ... ... ... ...
1368 2024/08/21 3.565 2524.408 692.0 720.0 678.0 715.0 27.0 4694.0
1369 2024/08/22 5.574 4162.601 723.0 776.0 723.0 742.0 27.0 7835.0
1370 2024/08/23 6.501 5037.483 727.0 814.0 726.0 807.0 65.0 7660.0
1371 2024/08/26 7.497 6126.218 809.0 860.0 771.0 798.0 -9.0 9371.0
1372 2024/08/27 4.870 3931.727 806.0 834.0 788.0 814.0 16.0 6800.0
1373 rows × 9 columns
# 計算移動平均線
def moving_average(data, window):
return data.rolling(window=window).mean()
def generate_signals(data, short_window, long_window):
signals = pd.DataFrame(index=data.index)
signals['price'] = data['Close']
signals['short_mavg'] = moving_average(data['Close'], short_window)
signals['long_mavg'] = moving_average(data['Close'], long_window)
signals['signal'] = 0.0
signals['signal'][short_window:] = np.where(signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:], 1.0, 0.0)
signals['positions'] = signals['signal'].diff()
return signals
# 短MA 採用 40天,長MA 採用 100天
signals = generate_signals(all_data, short_window=40, long_window=100)
signals
price short_mavg long_mavg signal positions
0 52.8 NaN NaN 0.0 NaN
1 52.8 NaN NaN 0.0 0.0
2 53.0 NaN NaN 0.0 0.0
3 53.0 NaN NaN 0.0 0.0
4 56.6 NaN NaN 0.0 0.0
... ... ... ... ... ...
1368 715.0 577.5375 491.890 1.0 0.0
1369 742.0 582.6875 496.220 1.0 0.0
1370 807.0 589.3875 501.245 1.0 0.0
1371 798.0 595.8625 506.105 1.0 0.0
1372 814.0 602.9625 511.035 1.0 0.0
1373 rows × 5 columns
然後就有東西了~
# 定義執行回測的函式,在這邊設定訊號、起始資金設10萬
def backtest(data, signals, initial_capital=100000.0):
positions = pd.DataFrame(index=signals.index).fillna(0.0) # 處理空值
positions['6223'] = 100 * signals['signal'] # 每次交易 100 股
portfolio = positions.multiply(data['Close'], axis=0)
pos_diff = positions.diff() # 每天持倉的變化
# 持有的股票倉位變化
portfolio['holdings'] = (positions.multiply(data['Close'], axis=0)).sum(axis=1)
# 現金水位 = 初始資金 - 持倉變化(ex. +100)*買或賣的價格(ex. 99)
portfolio['cash'] = initial_capital - (pos_diff.multiply(data['Close'], axis=0)).sum(axis=1).cumsum()
# 總資產的價值 = 現金 + 股票價值
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
return portfolio
# 執行回測
portfolio = backtest(all_data, signals)
portfolio
6223 holdings cash total
0 0.0 0.0 100000.0 100000.0
1 0.0 0.0 100000.0 100000.0
2 0.0 0.0 100000.0 100000.0
3 0.0 0.0 100000.0 100000.0
4 0.0 0.0 100000.0 100000.0
... ... ... ... ...
1368 71500.0 71500.0 83160.0 154660.0
1369 74200.0 74200.0 83160.0 157360.0
1370 80700.0 80700.0 83160.0 163860.0
1371 79800.0 79800.0 83160.0 162960.0
1372 81400.0 81400.0 83160.0 164560.0[1373 rows x 4 columns]
用以下幾個常見的評估指標:報酬(returns)
、年化報酬率(annual_return)
、年化波動率(annual_volatility)
、夏普比率(sharpe_ratio)
、最大回撤(drawdown)
,
特別解釋一下這幾個,
年化波動率: 可以衡量這個策略的風險,波動率高,代表風險高,但也意味著可以賺取更多報酬。
夏普比率: 每承擔一個單位的風險所能帶來的超額報酬,夏普比率越高,代表 報酬>風險。
最大回撤: 投資報酬從最高到最低會虧損的最大金額,可以用來衡量一個策略的泛化能力。
# 計算績效指標
def calculate_performance(portfolio):
returns = portfolio['total'].pct_change()
annual_return = (1 + returns.mean()) ** 252 - 1
annual_volatility = returns.std() * np.sqrt(252)
sharpe_ratio = returns.mean() / returns.std() * np.sqrt(252)
drawdown = (portfolio['total'].cummax() - portfolio['total']).max()
return annual_return, annual_volatility, sharpe_ratio, drawdown
annual_return, annual_volatility, sharpe_ratio, drawdown = calculate_performance(portfolio)
# 輸出績效指標
print(f'Annual Return: {annual_return:.2f}')
print(f'Annual Volatility: {annual_volatility:.2f}')
print(f'Sharpe Ratio: {sharpe_ratio:.2f}')
print(f'Max Drawdown: {drawdown:.2f}')
Annual Return: 0.10
Annual Volatility: 0.09
Sharpe Ratio: 1.05
Max Drawdown: 16000.00
# 繪製結果圖,用兩個子圖組成一個大圖,分別顯示 訊號買賣點、資產總值的變化
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 14))
# 繪製收盤價、短MA 和 長MA
all_data['Close'].plot(ax=ax1, color='black', lw=2.)
signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)
# 繪製買賣訊號
# 子圖ax1放收盤價、短MA、長MA、訊號標記
ax1.plot(signals.loc[signals.positions == 1.0].index,
signals.short_mavg[signals.positions == 1.0],
'^', markersize=10, color='m', label='buy signal')
ax1.plot(signals.loc[signals.positions == -1.0].index,
signals.short_mavg[signals.positions == -1.0],
'v', markersize=10, color='k', label='sell signal')
ax1.set_title('Moving Average Crossover Strategy')
ax1.set_ylabel('Price in NT$')
ax1.legend()
# 繪製資產變化
# 子圖ax2放回測期間資產的變化
portfolio['total'].plot(ax=ax2, lw=2.)
ax2.set_title('Portfolio Value')
ax2.set_ylabel('Total Value in NT$')
plt.show()
上面子圖的X軸用 回測的紀錄數 不太對,改用日期
。
在畫圖之前加入這行,把日期
欄位設成索引,再畫一次圖:
all_data.set_index('日期', inplace=True)
40MA和100MA的雙均線策略,後面一直沒有出現賣出訊號,
就直接結算到最後一天,難怪後面跟收盤價的走勢很像。
Annual Return: 0.08
Annual Volatility: 0.08
Sharpe Ratio: 1.02
Max Drawdown: 10900.00
參考文章&資料來源:
每日記錄:
加權指數收在22370.66點,上漲185.66點。
明天就要開NVDA財報了,未看先猜,應該是符合預期的,財報依然很好。
另外,還記得因為凱基經常會隔日沖,導致投資人以為市場看壞,
跟著殺進殺出,造成當天K棒收黑的可能性提高。
因此又有線型破壞者
之稱。